import { Action, ActionType, Activity, ActivityStatus, Autherizable,  ExecuteStatus, Executive, ExecutiveAction, ExecutiveContext, State } from "./defination"
import { ActivityRecord, Dealer, ExecutiveRecord } from "./entity"
import { CStateObject } from "./state"
import { ACTION, ACTIVITY_STATUS, EXECUTE_STATUS, GRAPH } from "./shared-symbols"
import { Graph, GraphAction } from "./graph"
import { readonly } from "./readonly"

/**
 * the object of the activity
 *
 * @export
 * @interface ExecutiveObject
 */
export class ExecutiveObject<
  TActivity extends ExecutiveObject<TActivity,TState,TRecord,TContext>,
  TState extends State=State,
  TRecord extends ExecutiveRecord=ExecutiveRecord,
  TContext extends ExecutiveContext=ExecutiveContext
> extends CStateObject<TActivity,TState,TRecord,TContext> 
implements Executive<TActivity,TState,TRecord,TContext>
{
  [ACTIVITY_STATUS]:ActivityStatus
  [EXECUTE_STATUS]:ExecuteStatus
  [ACTION]?:Action<TActivity,TState,TRecord,TContext>
  [GRAPH]:Graph
  constructor(record:TRecord,graph:Graph,parent?:TActivity, context?:TContext){
    super(record,parent,context)
    context = this.context
    this[GRAPH] = graph
    this[ACTIVITY_STATUS] = context.enumConverter.toValue<ActivityStatus>(record.activityStatus,ActivityStatus,true)!
    this[EXECUTE_STATUS] = context.enumConverter.toValue<ExecuteStatus>(record.executeStatus,ExecuteStatus,true)!
  }
  get activityStatus():ActivityStatus{
    return this[ACTIVITY_STATUS]
  }

  get executeStatus():ExecuteStatus{
    return this[EXECUTE_STATUS]
  } 
  get graph():Readonly<Graph> {
    return readonly(this[GRAPH])
  }
  
}



/**
 * invoke the action of the executive
 * if the executive has the action, invoke the action defined on the executive  
 * if the executive has the graph, invoke the action defined on the graph
 * if the executive has the default action, invoke the default action
 * @export
 * @template TActivity
 * @template TState
 * @template TRecord
 * @template TContext
 * @param {Executive<TActivity,TState,TRecord,TContext>} executive
 * @param {ActionType} actionType
 * @param {Dealer} dealer
 * @param {TContext} context
 * @param {GraphAction} [defaultAction]
 * @return {*}  {Promise<unknown>}
 */
export async function invokeExecutiveAction<
  TActivity extends Executive<TActivity,TState,TRecord,TContext>,
  TState extends State=State,
  TRecord extends ExecutiveRecord=ExecutiveRecord,
  TContext extends ExecutiveContext=ExecutiveContext
>(executive:Executive<TActivity,TState,TRecord,TContext>, actionType:ActionType,context:TContext,defaultAction?:GraphAction):Promise<unknown>{
  const actionName = ActionType[actionType]
  const dealer = context.dealer ?? context.request.dealer
  let result:unknown
  let hasResult = false
  // lower case : self only
  let graphAction = (executive.graph as {[action:string]: GraphAction})[actionName.toLowerCase()]
  // upper case : self and children
  if (!graphAction && actionType !== ActionType.DEAL){
    graphAction = (executive.graph as {[action:string]: GraphAction})[actionName]
  }
    if (graphAction) {
      if (typeof graphAction === 'string'){
        graphAction = context.actionFactory.getAction(graphAction,actionType,executive as unknown as Activity)
      }
      if (typeof graphAction !== 'function'){
        throw new Error('action is not a function')
      }
      result= graphAction(dealer,executive.state,executive  as unknown as Activity,context)
      hasResult = true  
    }

  
  if (!hasResult) {
    const executiveAction = (executive as unknown as {[action:string]: ExecutiveAction<TContext>})[actionName]
    if (executiveAction) {
      result = executiveAction.call(executive,context)
      hasResult = true
    }
  }
  
  if (!hasResult && defaultAction) {
    result = defaultAction(dealer,executive.state,executive  as unknown as Activity,context)
    hasResult = true
  }

  if(!hasResult && actionType !== ActionType.DEAL){
    const superResult = await callSuperAction(executive,actionType,context)
    if(superResult){
      result = superResult.returnValue
      hasResult = true
    }
  }

  if (hasResult) {  
    if (result instanceof Promise){
      return await result
    }else {
      return result
    }
  }
  return undefined
}



function callSuperAction<
  TActivity extends Executive<TActivity,TState,TRecord,TContext>,
  TState extends State=State,
  TRecord extends ExecutiveRecord=ExecutiveRecord,
  TContext extends ExecutiveContext=ExecutiveContext
>(act:Executive<TActivity,TState,TRecord,TContext>,actionType:ActionType,context:TContext):{returnValue:unknown}|undefined
{
  let lastAuthActivity = act
  let authActivity = act.parent
  const actionName = ActionType[actionType]
  while(authActivity){
    const graphAction = (authActivity.graph as {[action:string]: GraphAction})[actionName]
    if(graphAction){
      return {  returnValue: graphAction(context.dealer,authActivity.state,authActivity as unknown as Activity,context)}  
      
    }else{
      lastAuthActivity = authActivity
      authActivity = authActivity.parent
    }
  }
  // the top activity , is the flow activity , we need to invoke the auth method of the flow if it has one
  const executiveAction = (lastAuthActivity as unknown as {[action:string]: ExecutiveAction<TContext>})[actionName]
  if(executiveAction){
    return {returnValue: executiveAction.call(lastAuthActivity,context)}
  }
  return undefined
}